package com.spzx.product.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.PageHelper;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.StringUtils;
import com.spzx.common.core.web.controller.BaseController;
import com.spzx.common.core.web.domain.AjaxResult;
import com.spzx.common.core.web.page.TableDataInfo;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.SkuPriceVo;
import com.spzx.product.domain.Product;
import com.spzx.product.domain.ProductDetails;

import com.spzx.product.query.SkuQuery;
import com.spzx.product.service.*;
import com.spzx.product.vo.*;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author GAX
 * @date 2025-08-31
 */

@RequestMapping("channel")
@RestController
public class ChannelController extends BaseController {
    @Autowired
    private ICategoryService categoryService;

    @Autowired
    private IProductSkuService productSkuService;

    @Autowired
    private IBrandService brandService;

    @Autowired
    private ISkuStockService skuStockService;

    @Autowired
    private IProductService productService;

    @Autowired
    private IProductDetailsService productDetailsService;

    @Autowired
    private RedisTemplate redisTemplate;

    // 从 Spring 容器中注入我们自定义的线程池，后续的异步任务都将交由它来执行。
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Operation(summary = "首页数据")
    @GetMapping("index")
    public AjaxResult indexData() {
        // 创建首页 vo 对象
        IndexDataVo indexDataVo = new IndexDataVo();
        // 封装一级分类列表
        List<CategoryVo> categoryVoList = categoryService.getLevelOneCategory();
        indexDataVo.setCategoryList(categoryVoList);
        // 封装畅销 Top 的商品 SKU 列表
        List<ProductSkuVo> productSkuVoList = productSkuService.selectTop20Sale();
        indexDataVo.setProductSkuList(productSkuVoList);
        // 响应 vo
        return AjaxResult.success(indexDataVo);
    }

    // 查询所有的分类，一级，二级，三级都返回
    @Operation(summary = "查询所有的分类")
    @GetMapping("/category")
    public AjaxResult category() {
        return AjaxResult.success(categoryService.tree());
    }


    @Operation(summary = "分页条件查询商品 SKU列表")
    @GetMapping("/skuList/{pageNum}/{pageSize}")
    public TableDataInfo skuList(
            @PathVariable("pageNum") Integer pageNum,
            @PathVariable("pageSize") Integer pageSize,
            SkuQuery skuQuery
    ) {
        // 利用 PageHelper 设置分页参数到 ThreadLocal 中
        PageHelper.startPage(pageNum,pageSize);
        // 执行业务查询，此时 PageHelper 会自动拦截并处理分页逻辑
        List<ProductSkuVo> list = productSkuService.selectSkuList(skuQuery);
        // getDataTable：是若依 BaseController 中的方法
        // 底层使用 new PageInfo 包装 List 数据，并获取总记录数
        return getDataTable(list);
    }

    @Operation(summary = "获取全部品牌")
    @GetMapping("/brand")
    public AjaxResult selectBrandAll() {
        return success(brandService.list());
    }

    @Operation(summary = "封装商品 SKU 详情页所需数据")
    @GetMapping("item/{skuId}")
    public AjaxResult item(@PathVariable("skuId") Long skuId) {
        // 解决缓存穿透问题，判断用户访问商品 ID 是否存在
        Boolean flag = redisTemplate.opsForValue().getBit("product:sku:data", skuId);
        if (!flag) {
            throw new ServiceException("访问商品不存在!");
        }
        // 创建 itemVo 对象
        ItemVo itemVo = new ItemVo();

        // ① 根据 skuID，查询商品 sku 信息，由于要将商品信息放入到缓存中，所以此处不适用 MP 提供好的 getById
        // 异步编排优化：由于后续的任务-轮播图，图片详情，规格映射都需要商品 id，故此处直接由 sku 信息获取 id 返回

        // 任务1：查询 SKU 基本信息，并从中获取 productId 用于后续任务
        CompletableFuture<Long> productIdFuture = CompletableFuture.supplyAsync(() -> {
            ProductSku productSku = productSkuService.getSkuInfo(skuId);
            itemVo.setProductSku(productSku);
            return productSku.getProductId();
        }, threadPoolExecutor);


        // ② 根据 skuID，查询商品 sku 最新价格
        // 这里为什么还要刻意查询一下最新价格？因为商品详情会被缓存到 Redis 中
        // 商品详情页数据通常比较大（基本信息、属性、图片、库存、分类、营销活动信息…），为了减少数据库压力，会整体缓存到 Redis。
        // 但是价格、库存这类数据变化频率很高，不适合和详情页一起长期缓存
        // 如果 productSku 来自 缓存的商品详情，这个价格可能是缓存时刻的值，而不是此刻的实时价格。

        // 任务2：查询 SKU 最新价格 (独立，无需返回值)
        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            SkuPriceVo skuPrice = productSkuService.getSkuPrice(skuId);
            itemVo.setSkuPrice(skuPrice);
        }, threadPoolExecutor);


        // ③ 根据 skuID，查询商品的 sku 库存信息

        // 任务3：查询 SKU 库存信息 (独立，无需返回值)
        CompletableFuture<Void> stockFuture = CompletableFuture.runAsync(() -> {
            SkuStockVo skuStock = skuStockService.getSkuStock(skuId);
            itemVo.setSkuStock(skuStock);
        }, threadPoolExecutor);

        // ④ 查询商品的轮播图列表

        // 任务4：查询轮播图和规格 (依赖任务1的结果 productId)
        CompletableFuture<Void> sliderSpecFuture = productIdFuture.thenAcceptAsync(productId -> {
            Product product = productService.getById(productId);
            if (product != null && StringUtils.isNotBlank(product.getSliderUrls())) {
                // Arrays.stream 可以将数组转为一个 stream 流
                // 使用 String 的 split 方法，将 sliderUrls 字符串按逗号分割，得到一个 String 数组
                String[] sliderUrlList = Arrays.stream(product.getSliderUrls().split(","))
                        .map(String::trim)                  // 用 stream 流的 map 去掉可能的前后空格
                        .filter(StringUtils::isNotBlank)    // 用 stream 流的 filter 过滤掉空串
                        .toArray(String[]::new); // 等价于 (int size) -> new String[size]。将 stream 转为数组
                itemVo.setSliderUrlList(sliderUrlList);

                // ⑤ 查询商品的规格
                String specValue = product.getSpecValue();
                if (StringUtils.isNotBlank(specValue)) {
                    // 使用 JSON.parseArray() 方法将 JSON 字符串反序列化成一个 JSONArray 对象。
                    // JSONArray 是 Fastjson 库中的类，类似 Java 中可操作的列表
                    JSONArray specValueList = JSON.parseArray(specValue);
                    itemVo.setSpecValueList(specValueList);
                }
            } else {
                // 显式设置为空数组，避免前端收到 null
                itemVo.setSliderUrlList(new String[0]);
                itemVo.setSpecValueList(new JSONArray());
            }
        }, threadPoolExecutor);

        // ⑥ 查询商品详情图片列表

        // 任务5：查询商品详情图片 (依赖任务1的结果 productId)
        CompletableFuture<Void> detailsImageFuture = productIdFuture.thenAcceptAsync(productId -> {
            ProductDetails productDetails = productDetailsService.getOne(new LambdaQueryWrapper<ProductDetails>()
                    .eq(ProductDetails::getProductId, productId));
            if (productDetails != null && StringUtils.isNotBlank(productDetails.getImageUrls())) {
                String[] split = productDetails.getImageUrls().split(",");
                String[] strings = Arrays.stream(split).map(elm -> elm.trim()).filter(StringUtils::isNotBlank).toArray(String[]::new);
                itemVo.setDetailsImageUrlList(strings);
            } else {
                itemVo.setDetailsImageUrlList(new String[0]);
            }
        }, threadPoolExecutor);


        // ⑦ 封装商品的规格参数与 SKUID 的映射 Map
        // 用户在前端选择不同的规格组合（例如：颜色=红色，尺寸=XL），就会唯一确定一个SKU，而这个 SKU 有一个唯一的 ID。
        // 地址栏或页面状态的变化正是基于这个选中的 SKU ID
        // 使用 Map 来建立规格组合 -> SKU ID 的映射关系，前端只要查这个映射就能立刻知道选中组合对应的 SKU ID

        // 任务6：查询 SKU 规格映射 (依赖任务1的结果 productId)
        CompletableFuture<Void> skuMapFuture = productIdFuture.thenAcceptAsync(productId -> {
            Map<String, Long> skuSpecValueMap = productSkuService.getSkuSpecValue(productId);
            itemVo.setSkuSpecValueMap(skuSpecValueMap);
        }, threadPoolExecutor);

        // 将所有需要等待的最终任务组合起来，统一等待
        CompletableFuture.allOf(
                priceFuture,
                stockFuture,
                sliderSpecFuture,
                detailsImageFuture,
                skuMapFuture).join();
        return success(itemVo);
    }
}